//******************************************************************************
//this code is protected by the GNU affero GPLv3
//author:Sylvain BERTRAND <sylvain.bertrand AT gmail dot com>
//                        <digital.ragnarok AT gmail dot com>
//******************************************************************************
#include "globals.h"

static s8 evs_mode(struct client *c,s32 name,u32 id)
{
  struct output *o=&registry[name].output;
  s8 timing_idx=0;
  while(1){
    if(timing_idx==ALGA_TIMINGS_MAX) break;
    struct alga_timing *timing=
                           &o->dce_info.timings[o->blk_idx][timing_idx];
    if(timing->pixel_clk==0) break;

    u32 flgs=0;
    if(timing_idx==0) flgs|=WL_OUTPUT_MODE_PREFERED;
    if(o->current==timing) flgs|=WL_OUTPUT_MODE_CURRENT;

    //timing is horizontal/vertical, wayland is width/height
    s8 r=wl_output_ev_mode(c,id,flgs,timing->h,timing->v,v_refresh(timing));
    if(r==LWL_ERR) return LWL_ERR;//up the stack the client will be killed

    ++timing_idx;
  }
  return LWL_OK;
}

static s8 ev_geometry(struct client *c,s32 name,u32 id)
{
  struct output *o=&registry[name].output;
  //TODO:get the physical dims from driver, for now presume 4 pixels per
  //mm
  //TODO:investigate better heuristics for make/model strings
  s32 computed_physical_width=o->current->h/4;
  s32 computed_physical_height=o->current->v/4;
  void *model=&o->product_code[0];
  if(o->monitor_name[0]) model=&o->monitor_name[0];

  return wl_output_ev_geometry(c,id,o->x,o->y,computed_physical_width,
                    computed_physical_height,WL_OUTPUT_SUBPIXEL_UNKNOWN,
                      o->manufacturer,model,WL_OUTPUT_TRANSFORM_NORMAL);
}

static s8 bind_post(struct client *c,s32 name,u32 id)
{
  s8 r=evs_mode(c,name,id);
  if(r==LWL_ERR) return LWL_ERR;
  return ev_geometry(c,name,id);
}

struct gbl *output_gone(struct udev_device *d)
{
  s32 output_name=0;
  while(1){
    if(output_name==REGISTRY_N_MAX){
      PERR("output:%s:udev:unable to find an output for this display device\n",
                                                    udev_device_get_sysname(d));
      return 0;
    }
    if(registry[output_name].itf==WL_OUTPUT){
      if(registry[output_name].output.d!=0){
        //choose syspath as udev device uniq id
        void *syspath_udev=(void*)udev_device_get_syspath(d);
        void *syspath_output=(void*)udev_device_get_syspath(
                                                registry[output_name].output.d);
        if(!strcmp(syspath_udev,syspath_output)) break;
      }
    }
    ++output_name;
  }

  output_layout_row_del(output_name);
  dce_dpm_on(&registry[output_name].output);
  dce_double_frame_buffer_free(&registry[output_name].output);

  while(1){l r=close(registry[output_name].output.fd);if(r!=-EINTR) break;}
  udev_device_unref(registry[output_name].output.d);

  //means the output is gone but may not be released
  registry[output_name].output.d=0;

  //TODO:make leave all surfaces which entered this output

  registry_unpublish(output_name);
  return &registry[output_name];
}

//failing to configure an output should be able to fail without killing the
//server
struct gbl *output_plugged(struct udev_device *d)
{
  void *sysname=(void*)udev_device_get_sysname(d);
  if(!sysname){
    PERRC("output:udev:unable to get the sysname\n");
    goto err;
  }

  //----------------------------------------------------------------------------
 
  struct udev_device *p=udev_device_get_parent(d);//*no ref taken*
  if(!p){
    PERR("output:%s:udev:unable to get the parent device\n",sysname);
    goto err;
  }

  void *p_devnode=(void*)udev_device_get_devnode(p);
  if(!p_devnode){
    PERR("output:%s:udev:output parent device has no device node\n",sysname);
    goto err;
  }

  i fd;
  while(1){fd=(i)open(p_devnode,O_RDWR,0);if(fd!=-EINTR) break;}
  if(ISERR(fd)){
    PERR("output:%s:unable to open parent device node '%s'\n",sysname,
                                                                     p_devnode);
    goto err;
  }

  //----------------------------------------------------------------------------

  void *blk_idx_str=(void*)udev_device_get_sysattr_value(d,"block_index");
  if(!blk_idx_str){
    PERR("output:%s:udev:unable to retreive block index\n",sysname);
    goto err_close_fd;
  }

  u8 blk_idx;
  u8 r0=dec2u8(&blk_idx,blk_idx_str);
  if(!r0){
    PERR("output:%s:udev:unable to convert block index string %s\n",sysname,
                                                                   blk_idx_str);
    goto err_close_fd;
  }

  //----------------------------------------------------------------------------

  u64 discret_vram_sz=0;
  void *discret_vram_sz_str=(void*)udev_device_get_sysattr_value(p,
                                                                "discret_vram");
  if(discret_vram_sz_str){
    r0=dec2u64(&discret_vram_sz,discret_vram_sz_str);
    if(!r0){
      PERR("output:%s:udev:unable to convert discret vram size string %s\n",
                                                   sysname,discret_vram_sz_str);
      goto err_close_fd;
    }
  }else{
    PERR("output:%s:udev:no vram to book for frame buffer\n",sysname);
    goto err_close_fd; 
  }

  //----------------------------------------------------------------------------
  
  s32 output_name=0;
  while(1){
    if(output_name==REGISTRY_N_MAX){
      PERR("output:%s:no registry room\n",sysname);
      goto err_close_fd;
    }
    if(registry[output_name].itf==WL_INVALID) break;
    ++output_name;
  }

  registry[output_name].output.d=udev_device_ref(d);
  if(!registry[output_name].output.d){
    PERR("output:%s:unable to grab a ref on udev device\n",sysname);
    goto err_close_fd;
  }

  //----------------------------------------------------------------------------

  registry[output_name].output.discret_vram_sz=discret_vram_sz;
  registry[output_name].output.blk_idx=blk_idx;
  registry[output_name].output.fd=fd;

  //----------------------------------------------------------------------------

  void *manufacturer=(void*)udev_device_get_sysattr_value(d,"manufacturer");
  if(manufacturer){
    LOG_UDEV("output:%s:manufacturer:%s\n",sysname,manufacturer);
    strncpy(&registry[output_name].output.manufacturer[0],manufacturer,
                                                  OUTPUT_MANUFACTURER_SZ_MAX+1);
    registry[output_name].output.manufacturer[OUTPUT_MANUFACTURER_SZ_MAX]=0;
  }else registry[output_name].output.manufacturer[0]=0;

  //----------------------------------------------------------------------------

  void *product_code=(void*)udev_device_get_sysattr_value(d,"product_code");
  if(product_code){
    LOG_UDEV("output:%s:product code:%s\n",sysname,product_code);
    strncpy(&registry[output_name].output.product_code[0],product_code,
                                                  OUTPUT_PRODUCT_CODE_SZ_MAX+1);
    registry[output_name].output.product_code[OUTPUT_PRODUCT_CODE_SZ_MAX]=0;
  }else registry[output_name].output.product_code[0]=0;

  //----------------------------------------------------------------------------

  void *monitor_name=(void*)udev_device_get_sysattr_value(d,"monitor_name");
  if(monitor_name){
    LOG_UDEV("output:%s:monitor name:%s\n",sysname,monitor_name);
    strncpy(&registry[output_name].output.monitor_name[0],monitor_name,
                                                  OUTPUT_MONITOR_NAME_SZ_MAX+1);
    registry[output_name].output.monitor_name[OUTPUT_MONITOR_NAME_SZ_MAX]=0;
  }else registry[output_name].output.monitor_name[0]=0;

  //----------------------------------------------------------------------------

  void *monitor_serial=(void*)udev_device_get_sysattr_value(d,"monitor_serial");
  if(monitor_serial){
    LOG_UDEV("output:%s:monitor serial:%s\n",sysname,monitor_serial);
    strncpy(&registry[output_name].output.monitor_serial[0],monitor_serial,
                                                OUTPUT_MONITOR_SERIAL_SZ_MAX+1);
    registry[output_name].output.monitor_serial[OUTPUT_MONITOR_SERIAL_SZ_MAX]=0;
  }else registry[output_name].output.monitor_serial[0]=0;

  //----------------------------------------------------------------------------

  s8 r1=dce_info_get(output_name);
  if(r1!=LWL_OK) goto err_close_fd;

  struct cfg_output_info info;
  cfg_output_info_get(output_name,&info);

  r1=dce_mode_select(output_name,&info.mode[0]);
  if(r1!=LWL_OK) goto err_close_fd;

  r1=dce_mode_program(output_name);
  if(r1!=LWL_OK) goto err_close_fd;

  if(info.layout.x==CFG_OUTPUT_LAYOUT_X_NOT_SET
                                  &&info.layout.y==CFG_OUTPUT_LAYOUT_Y_NOT_SET){
    //no coords in compositor space provided in the conf file, then we install
    //the output in the "automatic" row layout
    r1=output_layout_row_add(output_name);
    if(r1!=LWL_OK) goto err_dpm_on;
  }else{
    //x and/or y were specified in the conf file, then the output is not in the
    //"automatic" row layout, then it's the float layout
    if(info.layout.x!=CFG_OUTPUT_LAYOUT_X_NOT_SET)
      registry[output_name].output.x=info.layout.x;
    else registry[output_name].output.x=0;

    if(info.layout.y!=CFG_OUTPUT_LAYOUT_Y_NOT_SET)
      registry[output_name].output.y=info.layout.y;
    else registry[output_name].output.y=0;
    LOG("output:%s:layout:float:insert coords (%d,%d)\n",sysname,
                                                registry[output_name].output.x,
                                                registry[output_name].output.y);
  }

  //commit the object in the registry
  registry[output_name].itf=WL_OUTPUT;
  registry[output_name].n=0;
  registry[output_name].is_singleton=0;
  registry[output_name].release=registry_gbl_generic_release;
  registry[output_name].bind_post=bind_post;

  registry_publish(output_name);
  return &registry[output_name];

err_dpm_on:
  dce_dpm_on(&registry[output_name].output);
  dce_double_frame_buffer_free(&registry[output_name].output);

err_close_fd:
  while(1){l r=close(fd);if(r!=-EINTR) break;}

err:
  return 0;
}
